package learn;

import graph.Graph;
import graph.graphNode;
import jTree.JunctionTree;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Scanner;
import java.util.regex.Pattern;

import queries.marginalQuery;
import riso.numerical.LBFGS;

public class Learn {
	Integer numRecords;
	Integer numFeatures;
	Integer numVariables;
	Integer arityVariables[];
	ArrayList<Record> recordList = new ArrayList<Record>();
	Feature[] featureList;
	
	Graph graph = new Graph();
	JunctionTree jt = null;
	marginalQuery mq = null;
	
	
	public Learn( String filename, Boolean useLBFG, Double eps){
		try {
		FileReader fr = 
				new FileReader(filename);
		
		Scanner scan = new Scanner(fr);
		
		read(scan);
		makeGraph();
		graph.traingulate();
		
		int n=numFeatures;
		int m=5;
		double weight[] = new double[n];
		double gweight[] = new double[n];
		boolean diagco = false;
		double diag[] = new double[n]; //dummy
		int iprint[] = new int[2];
		
		iprint [ 1 -1] = 0;
		iprint [ 2 -1] = 0;
		double xtol= 1.0e-16;
		int iflag[]=new int[2];
		iflag[0] = 0;

		Integer loop=1;
		long startTime = System.currentTimeMillis();
		
		Double LL1=1.0;
		Double LL2=1.0;
		Double LL=0.0; 

		for(;loop<500;loop++){
			makePotentials();
			jt = new JunctionTree(graph);
			//graph.print();
			
			calGradient();
			
			LL1 = getLogLikehood();
			//LL2 = normalize();
			LL = LL1 - numRecords * Math.log(LL2);
			
			System.err.println("LL1=" + LL1 +" LL2 = "+LL2);
			System.err.println("Likelihood=" + LL);
			
			/*
			for(int i=0;i<numFeatures;i++)
				System.err.println(""+featureList[i].toString());
			*/

			for(Integer i=0;i<numFeatures;i++){
				System.out.println(i+" "+featureList[i].weight);
			}
			
			if(useLBFG)
			{
				for(int i=0;i<numFeatures;i++){
					weight[i] = featureList[i].weight;
					gweight[i] = -1*featureList[i].gWeight;
				}
				
				try{
					LBFGS.lbfgs ( n , m , weight , Math.exp(-1*LL) , gweight , diagco , diag , iprint , eps , xtol , iflag );
				}catch (LBFGS.ExceptionWithIflag e)
				{
					System.err.println( "Sdrive: lbfgs failed.\n"+e );
					return;
				}
				if(iflag[0] == 0)
					break;
				for(int i=0;i<numFeatures;i++){
					featureList[i].weight = weight[i];
					featureList[i].gWeight = -1*gweight[i];
				}
			}else{
				Boolean doContinue = false;
				for(int i=0;i<numFeatures;i++){
					weight[i] = featureList[i].weight + 0.25*featureList[i].gWeight;
					if(Math.abs(weight[i]-featureList[i].weight) > eps)
						doContinue = true;
					featureList[i].weight = weight[i];
				}
				if(doContinue == false)
					break;
			}
		}
		
		long endTime = System.currentTimeMillis();

		System.out.println("\n\n\n************************************* ");
		System.out.println("LogLikelihood = "+ LL);
		System.out.println("Number of iterations = "+loop);
		System.out.println("Time(Milliseconds) = "+(endTime-startTime));
		for(Integer i=0;i<numFeatures;i++){
			System.out.println(i+" "+featureList[i].weight);
		}
		
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
	}
	


	@SuppressWarnings("unchecked")
	public void read(Scanner scan) {
		
		try{
		
		numRecords = scan.nextInt();
		numVariables = scan.nextInt();
		
		int arity = scan.nextInt();
		arityVariables = new Integer[numVariables];
		for(Integer i=0;i<numVariables;i++){
			arityVariables[i] = arity;
		}

		numFeatures = scan.nextInt();
		

		featureList = new Feature[numFeatures];
		for(Integer i=0;i<numFeatures;i++){
			
			Feature f = new Feature();
			f.length = scan.nextInt();
			int tnumVarSubset = scan.nextInt();
			
			String next1 = scan.nextLine();
			String na[] = next1.split(" |,");
			ArrayList<String> input = new ArrayList<String>();

			for(int zi=0;zi<na.length;zi++){
				na[zi] = na[zi].trim();
				if(na[zi].equals("")) continue;
				input.add(na[zi]);
			}
			
			for(Integer z=0;z<tnumVarSubset;z++){
				ArrayList<Integer> listVar = new ArrayList<Integer>();
				ArrayList<Integer> listVal = new ArrayList<Integer>();
				
				for(Integer zvar=0;zvar<f.length;zvar++){
					listVar.add( Integer.parseInt(input.get(0)) );
					input.remove(0);
				}
								
				
				for(Integer zval=0;zval<f.length;zval++){
					listVal.add( Integer.parseInt(input.get(0)) );
					input.remove(0);
				}
				
				
				f.set(i, listVar, listVal);
			}
			
			featureList[i] = f;
			f.numVarSubset = tnumVarSubset;
		}
		
		for(Integer i=0;i<numRecords;i++){
			Record rec = new Record();
			rec.read(scan, numVariables, this.featureList);
			recordList.add(rec);
		}
		
		}catch(Exception e){
			System.out.println(""+e);
		}
		
	}
	
	/**
	 * 
	 */
	@SuppressWarnings("static-access")
	public void makeGraph(){
		try {
			graph.numNodes = this.numVariables;
			graph.numEdges = 0;
            
            /** Init */
			graph.numEdgesAdded = 0;
			graph.Nodes = new boolean [(int)graph.numNodes][(int)graph.numNodes];
			graph.maxValues = new long [(int)graph.numNodes];
            
            for(Integer i=0;i<graph.numNodes;i++)
            	graph.maxValues[i] = 0;
            
            for(Integer i=0;i<graph.numNodes;i++)
                for(Integer j=0;j<graph.numNodes;j++)
                	graph.Nodes[i][j] = false;
            
            /** Read Maximum Value a node can take */
            for(Integer i=0;i<graph.numNodes;i++){
            	graph.maxValues[i] = this.arityVariables[i]; 
            }
            
            /** Connect nodes of features */
            for(Integer i=0;i<this.numFeatures;i++){ //features
            	for(Integer z=0;z<featureList[i].numVarSubset;z++){ // each variable subset
            		oneFeature one = featureList[i].list.get(z);
                	ArrayList<Integer> fvar = one.varList;
                	for(Integer j=0;j<fvar.size();j++){
                		Integer node1 = fvar.get(j);
                    	for(Integer k=j+1;k<fvar.size();k++){
                    		Integer node2 = fvar.get(k);
                        	graph.Nodes[node1][node2] = graph.Nodes[node2][node1] = true; 
                    	}
                	}            		
            	}
            }
            
            /* Write into file */
            
           
		
       }catch (Exception e) {
	            e.printStackTrace();
	     }
	}
	
   


	@SuppressWarnings("static-access")
	private void makePotentials() {
		/** Clear the potential Table */
		graph.ListOfPotentials = new ArrayList<graphNode>();
		
		ArrayList<ArrayList<Integer>> listVarCompleted = new ArrayList<ArrayList<Integer>>();
		
		int fNo, fVarSub;
		
		for(fNo=0; fNo<numFeatures;fNo++){
			Feature feature = featureList[fNo];
			for(fVarSub=0;fVarSub<feature.numVarSubset;fVarSub++){
				oneFeature one = feature.list.get(fVarSub);
				ArrayList<Integer> listVar = new ArrayList<Integer>(one.varList);	//Variable List 
				ArrayList<Integer> listVal = new ArrayList<Integer>();	//Value List 
				ArrayList<Integer> listFeatures = new ArrayList<Integer>();		//List of features, which has same set of Variables List
				//listFeatures.add(fNo);
				
				Boolean isAlreadyCalculated = false;
				for(Integer z=0;z<listVarCompleted.size();z++){
					if(oneFeature.isListSame(listVar, listVarCompleted.get(z))==true){
						isAlreadyCalculated = true;
						break;
					}
				}
				
				if(isAlreadyCalculated == false){
					listVarCompleted.add( new ArrayList<Integer>( listVar ) );
					
					for(Integer i=0;i<numFeatures;i++){
							if(featureList[i].isSameVarList(listVar)==true){
								listFeatures.add(i);
							}
					}
				
					/** START : Potential Table Calculation  */
					
					/** Create a new Potential Table */
					graphNode temp = new graphNode();
					
					/** Table Header, Variable List */
					temp.nodesPresent = new ArrayList<Integer>( listVar ) ;  

					for(Integer i=0;i<listVar.size();i++)
						listVal.add(0);
					
					do{
						/** Calculate the potential value */
						Double potVal = 0.0;
						for(Integer i=0;i<listFeatures.size();i++){
							int fNo1 = listFeatures.get(i);
							potVal += featureList[fNo1].getScore(listVar, listVal);
						}
						potVal = Math.exp(potVal);
						
						@SuppressWarnings("unchecked")
						ArrayList<Integer> listVal1 = new ArrayList<Integer>(listVal);
						temp.values.put(listVal1, potVal); // Table content Value + score
						
						/** Go for next Value */
						if(getNextValue(listVar, listVal)==false)
							break;
					}while(true);
					
					/** Add potential Table to Graph's List */
					graph.ListOfPotentials.add(temp); 	
					
					/** END : Potential Table Calculation  */

				}	// if(isAlreadyCalculated == false){
				
			} //	for(fVarSub=0;fVarSub<feature.numVarSubset;fVarSub++){
		}//	for(fNo=0; fNo<numFeatures;fNo++){
				
				
				 /** Print Table **/
		/*
				System.err.println("Tables Obtained ");
				for(int i = 0; i<graph.ListOfPotentials.size(); i++){
					System.err.println("The Nodes present in phi "+i+" are :");
					for(int j: graph.ListOfPotentials.get(i).nodesPresent)
						System.err.print(j+" ");
					System.err.println();
					
					for(List<Integer> keys : graph.ListOfPotentials.get(i).values.keySet()){
						for(int j : keys)
							System.err.print(j+" ");
						System.err.println(graph.ListOfPotentials.get(i).values.get(keys));
					}
				}
				
		*/
				
	}

	public Double getLogLikehood(){
		Double ll = 0.0;

		
		for(Integer ii=0;ii<numRecords;ii++){
			Record rec = recordList.get(ii);
			for(int i=0;i<numFeatures;i++){
				Feature f = featureList[i];
				for(int fVarSub=0;fVarSub<f.numVarSubset;fVarSub++){
					oneFeature one = f.list.get(fVarSub);
					if(isFeatureFired(rec.values, one.varList, one.valList)){
						ll += featureList[i].weight;
						//System.err.println("Fired "+f.fno+","+i+", var = " + one.varList + " val = " + one.valList );
					}
				}				
			}
		}
		
		return ll;
	}
	
	public Double normalize(){
		Double norm = 0.0;
		ArrayList<Integer> listVal = new ArrayList<Integer>();
		ArrayList<Integer> listVar = new ArrayList<Integer>();
		
		/*Initial values to ZERO */
		for(int i=0;i<numVariables;i++){
			listVar.add(i);
			listVal.add(0);
		}
		
		do{
			Double prob = 0.0;
			//System.err.println( "listVar = " + listVar);
			//System.err.println( "listVal = " + listVal);
			for(int i=0;i<numFeatures;i++){
				Feature f = featureList[i];
				for(int fVarSub=0;fVarSub<f.numVarSubset;fVarSub++){
					oneFeature one = f.list.get(fVarSub);
					if(isFeatureFired(listVal, one.varList, one.valList)){
						prob += featureList[i].weight;
						//System.err.println("Fired "+f.fno+","+i+", var = " + one.varList + " val = " + one.valList );
					}
				}				
			}
			norm += Math.exp(prob);
			/** Go for next Value */
			if(getNextValue(listVar, listVal)==false)
				break;
		}while(true);
		
		
		return norm;
	}
	
	public Boolean isFeatureFired( List<Integer> listVal, List<Integer> fVar, List<Integer> fVal){
		for(int i=0;i<fVar.size();i++){
			int variable = fVar.get(i);
			int value = fVal.get(i);
			if( listVal.get(variable) != value )
				return false;
		}
		return true;
	}

	private boolean getNextValue(ArrayList<Integer> listVar, ArrayList<Integer> listVal) {
		Integer indexChange = listVar.size() - 1;
		do{
			Integer var = listVar.get(indexChange);
			Integer val = listVal.get(indexChange);
			Integer newVal = val + 1;
			if( newVal >= arityVariables[var]){
				listVal.set(indexChange, 0);
				indexChange --;
			}else{
				listVal.set(indexChange, newVal);
				break;
			}
		} while(indexChange>=0);
		
		return indexChange>=0;
	}

	private void calGradient() {
		
		/** Reset */
		for(Integer i=0;i<numFeatures;i++){
			featureList[i].cntExpected =0.0;
			featureList[i].cntOberserved =0.0;
			featureList[i].gWeight =0.0;
		}
		
		/** Calculate Observed Count */
		
		for(Integer i=0;i<numRecords;i++){
			Record rec = recordList.get(i);
			for(int fNo=0;fNo<numFeatures;fNo++){
				Feature f = featureList[fNo];
				for(int fVarSub=0;fVarSub<f.numVarSubset;fVarSub++){
					oneFeature one = f.list.get(fVarSub);
					if(isFeatureFired(rec.values, one.varList, one.valList)){
						featureList[fNo].cntOberserved++;
						//System.err.println("Fired "+f.fno+","+i+", var = " + one.varList + " val = " + one.valList );
					}
				}				
			}
		}
		
		/** Calculate Expected Count */
		
		for(Integer i=0;i<numFeatures;i++){
			for(Integer j=0;j<featureList[i].numVarSubset;j++){
				oneFeature one = featureList[i].list.get(j);
				ArrayList<Integer> listVar = new ArrayList<Integer>(one.varList);
				ArrayList<Integer> listVal = new ArrayList<Integer>(one.valList);
				Double marProb1 = getMarProb(listVar, listVal);
				featureList[i].cntExpected += marProb1; 				
			}
		}
		
		/** Calculate Gradiant */
		
		for(Integer i=0;i<numFeatures;i++){
			featureList[i].gWeight = featureList[i].cntOberserved - numRecords * featureList[i].cntExpected ; 
		}
		
		
	}
	
	public Double getMarProb(ArrayList<Integer> listVar, ArrayList<Integer> listVal){
		Double prob = 0.0;
		//for(In)
		mq = new marginalQuery(listVar,jt);
		mq.getMarginal(mq.root,null,null);
		prob = mq.marginalTable.get(listVal);
		
		//System.err.println( "MarProb = var =" + listVar + " val = "+ listVal +" = " +prob );
		
		/*
		System.err.println("Marginal obtained");
		System.err.println("Val "+1+" Val 2 Probability");
	
		for(List<Integer> k : mq.marginalTable.keySet() ){
			for(int val: k)
				System.err.print(val+" ");
			System.err.println(mq.marginalTable.get(k));
		}
		*/
		return prob;
	}
	
	public Boolean isListEqual(List<Integer> A, List<Integer> B){
		if(A.size() != B.size())
			return false;
		for(Integer i=0;i<A.size();i++)
			if(A.get(i) != B.get(i))
				return false;
		return true;
	}
}
